空ではないS3バケットをTerraformのimportブロックを使ってまとめて削除する

空ではないS3バケットをTerraformのimportブロックを使ってまとめて削除する

シェルスクリプトを使わずにまとめて削除する方法の選択肢の一つとして。
Clock Icon2024.12.09

こんにちは、なおにしです。

不要な複数のS3バケットをTerraformのimportブロックを使ってtfstateに取り込んでまとめて削除してみたのでご紹介します。

はじめに

やりたかったことは、「バージョニングが有効になっていて現行バージョンのオブジェクトと削除マーカーが混在する複数のS3バケットをまとめて削除する」です。

上記を実施したい気持ちでとりあえずAWSマネジメントコンソール上から実行しようとすると以下の流れになるかと思います。

対象バケットを選択して[削除]。

2021209_bulk-delete-non-empty-s3-buckets-using-terraform-import-block_1.jpg

バケットが空ではないと怒られるから[バケットを空にする]。

2021209_bulk-delete-non-empty-s3-buckets-using-terraform-import-block_2.jpg

[完全に削除]と入力して[空にする]。

2021209_bulk-delete-non-empty-s3-buckets-using-terraform-import-block_3.jpg

削除が完了したので[終了]。

2021209_bulk-delete-non-empty-s3-buckets-using-terraform-import-block_4.jpg

もう一度[削除]。

2021209_bulk-delete-non-empty-s3-buckets-using-terraform-import-block_1.jpg

バケット名を入力して[バケットを削除]。

2021209_bulk-delete-non-empty-s3-buckets-using-terraform-import-block_5.jpg

削除完了。

2021209_bulk-delete-non-empty-s3-buckets-using-terraform-import-block_6.png

最初からまずバケットを空にしていればもう少し短いステップで削除できるわけですが、それでも正直面倒です。ましてや、バケットが数十以上ある場合、しかも以下の記事にもありますとおり直近でバケット数の上限も引き上げられたため、例えばそれなりの数のバケットを作成するような検証を実施した後、一つずつポチポチして削除するのはなかなかにストレスが溜まりそうです。

https://dev.classmethod.jp/articles/update-amazon-s3-buckets-default-10000/

そうするとCLIでまとめて削除したくなるわけですが、削除マーカーを考慮したりと色々と大変です。弊社内だけでも同様の旨を主張した記事が複数見つかることからも、悩ましい部分のようです。

https://dev.classmethod.jp/articles/aws-cli-remove-files-versioning-s3-bucket/

https://dev.classmethod.jp/articles/delete-s3-shell/

https://dev.classmethod.jp/articles/delete-versioning-s3-shell/

https://speakerdeck.com/emiki/baziyoninguyou-xiao-nas3baketutofalseobuziekutoxue-chu

つまりシェルスクリプトを書けば解決できるわけですが、スクリプトを読んで把握するのが大変だったので別のアプローチがないかなと考えてみたところ、ちょうどTerraformのimportブロックと繰り返し処理も活用してみたいなと思っていたので、標題のアプローチをやってみたという経緯です。

やってみた

事前準備

検証のために、冒頭に記載した「バージョニングが有効になっていて現行バージョンのオブジェクトと削除マーカーが混在する複数のS3バケット」を作成します。

以下のスクリプトを使用しました。

create-test-bucket.sh 
# エラーハンドリング
set -e

# スクリプトの設定

# バケット名のプレフィックス
BUCKET_PREFIX="test-naonishi"

# アップロードするサンプルオブジェクトの数
UPLOAD_COUNT=20

# 削除するサンプルオブジェクトの数
DELETE_COUNT=10

# リージョン設定
REGION="ap-northeast-1"

# 日付と乱数を使用してバケット名を生成
DATE=$(date +%Y%m%d)
RANDOM_NUM=$(od -An -N4 -D /dev/urandom | tr -d ' ')
BUCKET_NAME="${BUCKET_PREFIX}-${DATE}-${RANDOM_NUM}"

echo "作成するバケット名: $BUCKET_NAME"

# S3バケットを作成
echo "リージョン: $REGION を使用してバケットを作成します。"
aws s3api create-bucket --bucket "$BUCKET_NAME" --region "$REGION" --create-bucket-configuration LocationConstraint="$REGION"

echo "バケット '$BUCKET_NAME' をリージョン '$REGION' に作成しました。"

# バージョニングを有効にする
aws s3api put-bucket-versioning --bucket "$BUCKET_NAME" --versioning-configuration Status=Enabled

echo "バージョニングを有効にしました。"

# アップロードするオブジェクトの存在確認
echo "アップロードするオブジェクトの数: $UPLOAD_COUNT"

# UPLOAD_COUNT に基づくオブジェクトをアップロード
echo "$UPLOAD_COUNT 個のオブジェクトをアップロード中..."
for i in $(seq 1 "$UPLOAD_COUNT")
do
    OBJECT_KEY="object_$i.txt"
    echo "これはオブジェクト番号 $i です。" > "/tmp/$OBJECT_KEY"
    aws s3 cp "/tmp/$OBJECT_KEY" "s3://$BUCKET_NAME/$OBJECT_KEY"
    rm "/tmp/$OBJECT_KEY"
done
echo "$UPLOAD_COUNT 個のオブジェクトをアップロードしました。"

# 削除するオブジェクトの存在確認
echo "削除するオブジェクトの数: $DELETE_COUNT"

# DELETE_COUNT 個のオブジェクトを削除
echo "$DELETE_COUNT 個のオブジェクトを削除中..."
for i in $(seq 1 "$DELETE_COUNT")
do
    OBJECT_KEY="object_$i.txt"
    aws s3 rm "s3://$BUCKET_NAME/$OBJECT_KEY"
done
echo "$DELETE_COUNT 個のオブジェクトを削除しました。"

# バケット内のオブジェクトと削除マーカーを表示
echo "バケット内のオブジェクトと削除マーカー一覧:"

# オブジェクトのバージョンと削除マーカーを取得
aws s3api list-object-versions --bucket "$BUCKET_NAME" --output json > /tmp/object_versions.json

# オブジェクトの一覧表示
echo "===== オブジェクト ====="
aws s3 ls "s3://$BUCKET_NAME/" --recursive

# 削除マーカーの一覧表示
echo "===== 削除マーカー ====="
if jq -e '.DeleteMarkers[]' /tmp/object_versions.json > /dev/null; then
    jq -r '.DeleteMarkers[] | "Key: \(.Key), VersionId: \(.VersionId), IsLatest: \(.IsLatest)"' /tmp/object_versions.json
else
    echo "削除マーカーは存在しません。"
fi

# 一時ファイルの削除
rm /tmp/object_versions.json

もし実際に実行される場合は「BUCKET_PREFIX」を適宜変更してください。実行すると以下のようにログが出力されます。

$ bash ./create-test-bucket.sh
作成するバケット名: test-naonishi-20241206-1387556774
リージョン: ap-northeast-1 を使用してバケットを作成します。
{
    "Location": "http://test-naonishi-20241206-1387556774.s3.amazonaws.com/"
}
バケット 'test-naonishi-20241206-1387556774' をリージョン 'ap-northeast-1' に作成しました。
バージョニングを有効にしました。
アップロードするオブジェクトの数: 20
20 個のオブジェクトをアップロード中...
upload: ../../../../../../tmp/object_1.txt to s3://test-naonishi-20241206-1387556774/object_1.txt
upload: ../../../../../../tmp/object_2.txt to s3://test-naonishi-20241206-1387556774/object_2.txt
upload: ../../../../../../tmp/object_3.txt to s3://test-naonishi-20241206-1387556774/object_3.txt
upload: ../../../../../../tmp/object_4.txt to s3://test-naonishi-20241206-1387556774/object_4.txt
upload: ../../../../../../tmp/object_5.txt to s3://test-naonishi-20241206-1387556774/object_5.txt
upload: ../../../../../../tmp/object_6.txt to s3://test-naonishi-20241206-1387556774/object_6.txt
upload: ../../../../../../tmp/object_7.txt to s3://test-naonishi-20241206-1387556774/object_7.txt
upload: ../../../../../../tmp/object_8.txt to s3://test-naonishi-20241206-1387556774/object_8.txt
upload: ../../../../../../tmp/object_9.txt to s3://test-naonishi-20241206-1387556774/object_9.txt
upload: ../../../../../../tmp/object_10.txt to s3://test-naonishi-20241206-1387556774/object_10.txt
upload: ../../../../../../tmp/object_11.txt to s3://test-naonishi-20241206-1387556774/object_11.txt
upload: ../../../../../../tmp/object_12.txt to s3://test-naonishi-20241206-1387556774/object_12.txt
upload: ../../../../../../tmp/object_13.txt to s3://test-naonishi-20241206-1387556774/object_13.txt
upload: ../../../../../../tmp/object_14.txt to s3://test-naonishi-20241206-1387556774/object_14.txt
upload: ../../../../../../tmp/object_15.txt to s3://test-naonishi-20241206-1387556774/object_15.txt
upload: ../../../../../../tmp/object_16.txt to s3://test-naonishi-20241206-1387556774/object_16.txt
upload: ../../../../../../tmp/object_17.txt to s3://test-naonishi-20241206-1387556774/object_17.txt
upload: ../../../../../../tmp/object_18.txt to s3://test-naonishi-20241206-1387556774/object_18.txt
upload: ../../../../../../tmp/object_19.txt to s3://test-naonishi-20241206-1387556774/object_19.txt
upload: ../../../../../../tmp/object_20.txt to s3://test-naonishi-20241206-1387556774/object_20.txt
20 個のオブジェクトをアップロードしました。
削除するオブジェクトの数: 10
10 個のオブジェクトを削除中...
delete: s3://test-naonishi-20241206-1387556774/object_1.txt
delete: s3://test-naonishi-20241206-1387556774/object_2.txt
delete: s3://test-naonishi-20241206-1387556774/object_3.txt
delete: s3://test-naonishi-20241206-1387556774/object_4.txt
delete: s3://test-naonishi-20241206-1387556774/object_5.txt
delete: s3://test-naonishi-20241206-1387556774/object_6.txt
delete: s3://test-naonishi-20241206-1387556774/object_7.txt
delete: s3://test-naonishi-20241206-1387556774/object_8.txt
delete: s3://test-naonishi-20241206-1387556774/object_9.txt
delete: s3://test-naonishi-20241206-1387556774/object_10.txt
10 個のオブジェクトを削除しました。
バケット内のオブジェクトと削除マーカー一覧:
===== オブジェクト =====
2024-12-06 18:11:34         47 object_11.txt
2024-12-06 18:11:34         47 object_12.txt
2024-12-06 18:11:35         47 object_13.txt
2024-12-06 18:11:36         47 object_14.txt
2024-12-06 18:11:37         47 object_15.txt
2024-12-06 18:11:38         47 object_16.txt
2024-12-06 18:11:38         47 object_17.txt
2024-12-06 18:11:39         47 object_18.txt
2024-12-06 18:11:40         47 object_19.txt
2024-12-06 18:11:41         47 object_20.txt
===== 削除マーカー =====
Key: object_1.txt, VersionId: 7QLMsEDTrW6MyuF5WvTFu.KdqVuswKo., IsLatest: true
Key: object_10.txt, VersionId: .5SN92YL6lxH7PJNlGP4ePJimPNOze1l, IsLatest: true
Key: object_2.txt, VersionId: QiYv4i5OQLkA.SrMtQ6lPoquM49a1yL1, IsLatest: true
Key: object_3.txt, VersionId: 9_XpfMBBYKXk_0qIWwe7EQZo6EHUfZ9Q, IsLatest: true
Key: object_4.txt, VersionId: o7.c_LVKyyTJZeTysAAM41AtSDfX_yDJ, IsLatest: true
Key: object_5.txt, VersionId: h.74fzSnKcfOTKzp78pYudaTMcTGTZtU, IsLatest: true
Key: object_6.txt, VersionId: K7YVfQptRY8ptug87QIe0K6chEQWiZxF, IsLatest: true
Key: object_7.txt, VersionId: fdHwlvAdySbpO57LK5fXL2dct.5kDM9L, IsLatest: true
Key: object_8.txt, VersionId: l5fjlYMAiKWMeDmyUjQx2h6Vep8OykIZ, IsLatest: true
Key: object_9.txt, VersionId: vMgasoFGUIGf1PLl3WZTloMOiFv3tby5, IsLatest: true
$

以下のようにオブジェクトが格納されたバケットが作成されます。バージョニングを有効にしているため、削除したオブジェクトについては削除マーカーが作成されていることが確認できます。

2021209_bulk-delete-non-empty-s3-buckets-using-terraform-import-block_7.png

スクリプトを複数回実行して、今回は上記のようなバケットを10個作成しました。

2021209_bulk-delete-non-empty-s3-buckets-using-terraform-import-block_8.png

S3バケットの削除

削除に使用する主なTerraform HCLは以下の記述だけです。簡略のためにvariableブロックも同ファイル内に記述していますので、terraformブロックとproviderブロックだけ別途ご準備ください。なお、importブロックのidに変数やリソース属性などの値が参照できるようになったのはTerraform バージョン1.6 以降ですのでご注意ください。その他に必要なファイルとしては削除対象のS3バケット一覧のリストを記載した「terraform.tfvars」になりますが、こちらは後述します。

main.tf
variable "s3_buckets" {
  type = list(string)
}

locals {
  s3_bucket_map = { for bucket in var.s3_buckets : bucket => bucket }
}

import {
  for_each = local.s3_bucket_map
  id       = each.value
  to       = aws_s3_bucket.buckets[each.key]
}

resource "aws_s3_bucket" "buckets" {
  for_each      = local.s3_bucket_map
  bucket        = each.key
  force_destroy = true
}

順番に見ていきます。
variableブロックについては前述のとおり「terraform.tfvars」に記載されたリストを参照します。
localsブロックではvariableブロックで読み込んだバケット名をローカル変数として定義します。例えば「terraform.tfvars」内で3つのバケット「バケットA、バケットB、バケットC」をリストとして記載していた場合は、以下のように各キー・バリューの組み合わせを持つマップになります。

s3_bucket_map = { 
	バケットA = "バケットA"
	バケットB = "バケットB"
	バケットC = "バケットC"
}

importブロックでは作成されたマップ「s3_bucket_map」のキー・バリューごとにS3バケットリソースの取り込みを行います。この際、例えばバケットAについて処理する時(each.key = バケットA、each.value = "バケットA"の時)、"aws_s3_bucket"リソースが参照されますが、こちらでも同じマップ「s3_bucket_map」を基に処理しているため、一貫性のあるループ処理となります。
参照されたresourceブロックの"aws_s3_bucket"リソースでは、追加の引数として「force_destroy」を有効にしています。このため、既存のS3バケットをtfstateに取り込む際、「force_destroy」が有効になることで「terraform destroy」を実行するときにS3バケット内の全てのオブジェクトをまとめて削除することができるようになります。

それでは事前準備で作成した検証用のS3バケットを実際に削除してみます。まず、variableブロックで読み取るための「terraform.tfvars」を作成します。

特定のプレフィックスが決まっているS3バケットを削除する場合であれば、例えば以下のようにjqコマンドで加工することで一覧をリスト形式で出力可能です。

aws s3api list-buckets | jq -r --arg PREFIX "test-naonishi" '
  "s3_buckets = [\n" +
    ( [.Buckets[].Name | select(startswith($PREFIX))] 
      | map("  \"" + . + "\"") 
      | join(",\n") 
    ) + 
    "\n]"
' > terraform.tfvars

$ cat terraform.tfvars                                         
s3_buckets = [
  "test-naonishi-20241206-1276690069",
  "test-naonishi-20241206-1332531992",
  "test-naonishi-20241206-1387556774",
  "test-naonishi-20241206-1750327978",
  "test-naonishi-20241206-202977240",
  "test-naonishi-20241206-208776533",
  "test-naonishi-20241206-227125234",
  "test-naonishi-20241209-1310875339",
  "test-naonishi-20241209-3056722394",
  "test-naonishi-20241209-36887086"
]

準備ができましたのでTerraformを実行していきます。

$ terraform init                                               
〜〜〜〜〜(略)〜〜〜〜〜
$ terraform plan
aws_s3_bucket.buckets["test-naonishi-20241206-1750327978"]: Preparing import... [id=test-naonishi-20241206-1750327978]
aws_s3_bucket.buckets["test-naonishi-20241206-1387556774"]: Preparing import... [id=test-naonishi-20241206-1387556774]
〜〜〜〜〜(略)〜〜〜〜〜
Terraform will perform the following actions:

  # aws_s3_bucket.buckets["test-naonishi-20241206-1276690069"] will be updated in-place
  # (imported from "test-naonishi-20241206-1276690069")
  ~ resource "aws_s3_bucket" "buckets" {
        acceleration_status         = null
        arn                         = "arn:aws:s3:::test-naonishi-20241206-1276690069"
        bucket                      = "test-naonishi-20241206-1276690069"
        bucket_domain_name          = "test-naonishi-20241206-1276690069.s3.amazonaws.com"
        bucket_prefix               = null
        bucket_regional_domain_name = "test-naonishi-20241206-1276690069.s3.ap-northeast-1.amazonaws.com"
      + force_destroy               = true
        hosted_zone_id              = "Z2M4EHUR26P7ZW"
        id                          = "test-naonishi-20241206-1276690069"
        object_lock_enabled         = false
        policy                      = null
        region                      = "ap-northeast-1"
        request_payer               = "BucketOwner"
        tags                        = {}
        tags_all                    = {}

        grant {
            id          = "a488157ce1f8c54fd7666a10a4ed096882cdd710046d71906dbcb0e7dcad8be5"
            permissions = [
                "FULL_CONTROL",
            ]
            type        = "CanonicalUser"
            uri         = null
        }

        server_side_encryption_configuration {
            rule {
                bucket_key_enabled = false

                apply_server_side_encryption_by_default {
                    kms_master_key_id = null
                    sse_algorithm     = "AES256"
                }
            }
        }

        versioning {
            enabled    = true
            mfa_delete = false
        }
    }
〜〜〜〜〜(略。残り9個分のバケット情報が上記と同様に出力される)〜〜〜〜〜
Plan: 10 to import, 0 to add, 10 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

各S3バケットで「force_destroy」が有効になっていること、10個分がインポートされることが確認できました。

$ terraform apply
aws_s3_bucket.buckets["test-naonishi-20241206-1276690069"]: Preparing import... [id=test-naonishi-20241206-1276690069]
aws_s3_bucket.buckets["test-naonishi-20241206-1750327978"]: Preparing import... [id=test-naonishi-20241206-1750327978]
aws_s3_bucket.buckets["test-naonishi-20241206-202977240"]: Preparing import... [id=test-naonishi-20241206-202977240]
aws_s3_bucket.buckets["test-naonishi-20241209-1310875339"]: Preparing import... [id=test-naonishi-20241209-1310875339]
〜〜〜〜〜(略)〜〜〜〜〜
Terraform will perform the following actions:

  # aws_s3_bucket.buckets["test-naonishi-20241206-1276690069"] will be updated in-place
  # (imported from "test-naonishi-20241206-1276690069")
  ~ resource "aws_s3_bucket" "buckets" {
        acceleration_status         = null
        arn                         = "arn:aws:s3:::test-naonishi-20241206-1276690069"
        bucket                      = "test-naonishi-20241206-1276690069"
        bucket_domain_name          = "test-naonishi-20241206-1276690069.s3.amazonaws.com"
        bucket_prefix               = null
        bucket_regional_domain_name = "test-naonishi-20241206-1276690069.s3.ap-northeast-1.amazonaws.com"
      + force_destroy               = true
        hosted_zone_id              = "Z2M4EHUR26P7ZW"
        id                          = "test-naonishi-20241206-1276690069"
        object_lock_enabled         = false
        policy                      = null
        region                      = "ap-northeast-1"
        request_payer               = "BucketOwner"
        tags                        = {}
        tags_all                    = {}

        grant {
            id          = "a488157ce1f8c54fd7666a10a4ed096882cdd710046d71906dbcb0e7dcad8be5"
            permissions = [
                "FULL_CONTROL",
            ]
            type        = "CanonicalUser"
            uri         = null
        }

        server_side_encryption_configuration {
            rule {
                bucket_key_enabled = false

                apply_server_side_encryption_by_default {
                    kms_master_key_id = null
                    sse_algorithm     = "AES256"
                }
            }
        }

        versioning {
            enabled    = true
            mfa_delete = false
        }
    }
〜〜〜〜〜(略)〜〜〜〜〜
Plan: 10 to import, 0 to add, 10 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_s3_bucket.buckets["test-naonishi-20241206-1387556774"]: Importing... [id=test-naonishi-20241206-1387556774]
aws_s3_bucket.buckets["test-naonishi-20241206-1387556774"]: Import complete [id=test-naonishi-20241206-1387556774]
aws_s3_bucket.buckets["test-naonishi-20241206-227125234"]: Importing... [id=test-naonishi-20241206-227125234]
〜〜〜〜〜(略)〜〜〜〜〜
Apply complete! Resources: 10 imported, 0 added, 10 changed, 0 destroyed.

$ terraform state list
aws_s3_bucket.buckets["test-naonishi-20241206-1276690069"]
aws_s3_bucket.buckets["test-naonishi-20241206-1332531992"]
aws_s3_bucket.buckets["test-naonishi-20241206-1387556774"]
aws_s3_bucket.buckets["test-naonishi-20241206-1750327978"]
aws_s3_bucket.buckets["test-naonishi-20241206-202977240"]
aws_s3_bucket.buckets["test-naonishi-20241206-208776533"]
aws_s3_bucket.buckets["test-naonishi-20241206-227125234"]
aws_s3_bucket.buckets["test-naonishi-20241209-1310875339"]
aws_s3_bucket.buckets["test-naonishi-20241209-3056722394"]
aws_s3_bucket.buckets["test-naonishi-20241209-36887086"]

削除対象である10個の検証用S3バケットがtfstateに取り込まれました。

それでは削除します。

$ terraform destroy    
aws_s3_bucket.buckets["test-naonishi-20241206-227125234"]: Refreshing state... [id=test-naonishi-20241206-227125234]
aws_s3_bucket.buckets["test-naonishi-20241206-208776533"]: Refreshing state... [id=test-naonishi-20241206-208776533]
aws_s3_bucket.buckets["test-naonishi-20241209-36887086"]: Refreshing state... [id=test-naonishi-20241209-36887086]
〜〜〜〜〜(略)〜〜〜〜〜
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_s3_bucket.buckets["test-naonishi-20241206-1276690069"] will be destroyed
  - resource "aws_s3_bucket" "buckets" {
      - arn                         = "arn:aws:s3:::test-naonishi-20241206-1276690069" -> null
      - bucket                      = "test-naonishi-20241206-1276690069" -> null
      - bucket_domain_name          = "test-naonishi-20241206-1276690069.s3.amazonaws.com" -> null
      - bucket_regional_domain_name = "test-naonishi-20241206-1276690069.s3.ap-northeast-1.amazonaws.com" -> null
      - force_destroy               = true -> null
      - hosted_zone_id              = "Z2M4EHUR26P7ZW" -> null
      - id                          = "test-naonishi-20241206-1276690069" -> null
      - object_lock_enabled         = false -> null
      - region                      = "ap-northeast-1" -> null
      - request_payer               = "BucketOwner" -> null
      - tags                        = {} -> null
      - tags_all                    = {} -> null
        # (3 unchanged attributes hidden)

      - grant {
          - id          = "a488157ce1f8c54fd7666a10a4ed096882cdd710046d71906dbcb0e7dcad8be5" -> null
          - permissions = [
              - "FULL_CONTROL",
            ] -> null
          - type        = "CanonicalUser" -> null
            # (1 unchanged attribute hidden)
        }

      - server_side_encryption_configuration {
          - rule {
              - bucket_key_enabled = false -> null

              - apply_server_side_encryption_by_default {
                  - sse_algorithm     = "AES256" -> null
                    # (1 unchanged attribute hidden)
                }
            }
        }

      - versioning {
          - enabled    = true -> null
          - mfa_delete = false -> null
        }
    }
〜〜〜〜〜(略)〜〜〜〜〜
Plan: 0 to add, 0 to change, 10 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_s3_bucket.buckets["test-naonishi-20241206-208776533"]: Destroying... [id=test-naonishi-20241206-208776533]
aws_s3_bucket.buckets["test-naonishi-20241206-1750327978"]: Destroying... [id=test-naonishi-20241206-1750327978]
aws_s3_bucket.buckets["test-naonishi-20241206-1332531992"]: Destroying... [id=test-naonishi-20241206-1332531992]
aws_s3_bucket.buckets["test-naonishi-20241206-202977240"]: Destroying... [id=test-naonishi-20241206-202977240]
aws_s3_bucket.buckets["test-naonishi-20241209-36887086"]: Destroying... [id=test-naonishi-20241209-36887086]
aws_s3_bucket.buckets["test-naonishi-20241209-1310875339"]: Destroying... [id=test-naonishi-20241209-1310875339]
aws_s3_bucket.buckets["test-naonishi-20241206-1387556774"]: Destroying... [id=test-naonishi-20241206-1387556774]
aws_s3_bucket.buckets["test-naonishi-20241209-3056722394"]: Destroying... [id=test-naonishi-20241209-3056722394]
aws_s3_bucket.buckets["test-naonishi-20241206-1276690069"]: Destroying... [id=test-naonishi-20241206-1276690069]
aws_s3_bucket.buckets["test-naonishi-20241206-227125234"]: Destroying... [id=test-naonishi-20241206-227125234]
aws_s3_bucket.buckets["test-naonishi-20241206-202977240"]: Destruction complete after 2s
aws_s3_bucket.buckets["test-naonishi-20241209-36887086"]: Destruction complete after 2s
aws_s3_bucket.buckets["test-naonishi-20241206-1332531992"]: Destruction complete after 2s
aws_s3_bucket.buckets["test-naonishi-20241206-1750327978"]: Destruction complete after 2s
aws_s3_bucket.buckets["test-naonishi-20241209-1310875339"]: Destruction complete after 2s
aws_s3_bucket.buckets["test-naonishi-20241206-1276690069"]: Destruction complete after 2s
aws_s3_bucket.buckets["test-naonishi-20241206-1387556774"]: Destruction complete after 2s
aws_s3_bucket.buckets["test-naonishi-20241206-227125234"]: Destruction complete after 2s
aws_s3_bucket.buckets["test-naonishi-20241206-208776533"]: Destruction complete after 2s
aws_s3_bucket.buckets["test-naonishi-20241209-3056722394"]: Destruction complete after 2s

Destroy complete! Resources: 10 destroyed.
$ terraform state list
(何も出力されない)
$ terraform state pull 
{
  "version": 4,
  "terraform_version": "1.9.2",
  "serial": 21,
  "lineage": "33e8378b-61ba-6dbb-d688-344b387ae8fb",
  "outputs": {},
  "resources": [],
  "check_results": null
}

対象のS3バケットだけが無事に削除されたことを確認できました。

2021209_bulk-delete-non-empty-s3-buckets-using-terraform-import-block_9.png

なお、同様の方法で大量のオブジェクトが格納されているS3バケットを消す場合はある程度時間がかかります。格納オブジェクト数を確認したログを取得し忘れてしまったのですが、以下の例は数千オブジェクトが格納されたS3バケット1つを削除してみたログです。

$ terraform destroy
aws_s3_bucket.main: Refreshing state... [id=dlt-dltcommonresourceslogsbucket48a2774d-efamnkvtij73]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_s3_bucket.main will be destroyed
  - resource "aws_s3_bucket" "main" {
      - arn                         = "arn:aws:s3:::dlt-dltcommonresourceslogsbucket48a2774d-efamnkvtij73" -> null
      - bucket                      = "dlt-dltcommonresourceslogsbucket48a2774d-efamnkvtij73" -> null
      - bucket_domain_name          = "dlt-dltcommonresourceslogsbucket48a2774d-efamnkvtij73.s3.amazonaws.com" -> null
      - bucket_regional_domain_name = "dlt-dltcommonresourceslogsbucket48a2774d-efamnkvtij73.s3.ap-northeast-1.amazonaws.com" -> null
      - force_destroy               = true -> null
      - hosted_zone_id              = "Z2M4EHUR26P7ZW" -> null
      - id                          = "dlt-dltcommonresourceslogsbucket48a2774d-efamnkvtij73" -> null
      - object_lock_enabled         = false -> null
      - region                      = "ap-northeast-1" -> null
      - request_payer               = "BucketOwner" -> null
      - tags                        = {} -> null
      - tags_all                    = {
          - "CmBillingGroup" = "default"
        } -> null
        # (3 unchanged attributes hidden)

      - grant {
            id          = null
          - permissions = [
              - "READ_ACP",
              - "WRITE",
            ] -> null
          - type        = "Group" -> null
          - uri         = "http://acs.amazonaws.com/groups/s3/LogDelivery" -> null
        }
      - grant {
          - id          = "a488157ce1f8c54fd7666a10a4ed096882cdd710046d71906dbcb0e7dcad8be5" -> null
          - permissions = [
              - "FULL_CONTROL",
            ] -> null
          - type        = "CanonicalUser" -> null
            # (1 unchanged attribute hidden)
        }
      - grant {
          - id          = "c4c1ede66af53448b93c283ce9448c4ba468c9432aa01d700d3878632f77d2d0" -> null
          - permissions = [
              - "FULL_CONTROL",
            ] -> null
          - type        = "CanonicalUser" -> null
            # (1 unchanged attribute hidden)
        }

      - server_side_encryption_configuration {
          - rule {
              - bucket_key_enabled = false -> null

              - apply_server_side_encryption_by_default {
                  - sse_algorithm     = "AES256" -> null
                    # (1 unchanged attribute hidden)
                }
            }
        }

      - versioning {
          - enabled    = false -> null
          - mfa_delete = false -> null
        }
    }

Plan: 0 to add, 0 to change, 1 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_s3_bucket.main: Destroying... [id=dlt-dltcommonresourceslogsbucket48a2774d-efamnkvtij73]
aws_s3_bucket.main: Still destroying... [id=dlt-dltcommonresourceslogsbucket48a2774d-efamnkvtij73, 10s elapsed]
aws_s3_bucket.main: Still destroying... [id=dlt-dltcommonresourceslogsbucket48a2774d-efamnkvtij73, 20s elapsed]
aws_s3_bucket.main: Still destroying... [id=dlt-dltcommonresourceslogsbucket48a2774d-efamnkvtij73, 30s elapsed]
aws_s3_bucket.main: Still destroying... [id=dlt-dltcommonresourceslogsbucket48a2774d-efamnkvtij73, 40s elapsed]
aws_s3_bucket.main: Still destroying... [id=dlt-dltcommonresourceslogsbucket48a2774d-efamnkvtij73, 50s elapsed]
aws_s3_bucket.main: Still destroying... [id=dlt-dltcommonresourceslogsbucket48a2774d-efamnkvtij73, 1m0s elapsed]
aws_s3_bucket.main: Still destroying... [id=dlt-dltcommonresourceslogsbucket48a2774d-efamnkvtij73, 1m10s elapsed]
aws_s3_bucket.main: Destruction complete after 1m15s

Destroy complete! Resources: 1 destroyed.

まとめ

S3バケットの削除がまずはオブジェクトや削除マーカーを削除してからというステップになっていること、および一括削除という操作が簡単にはできないようになっていることは、S3バケットを誤って削除してしまうことの防止にもつながっているかと思います。

シェルスクリプトを使用した方法や本記事の方法でS3バケットをより効率的に削除することが可能になるかとは思いますが、実際に削除する際はくれぐれも意図しないS3バケットを対象にしないようにご注意ください。

ちなみに私は以下の過去記事でDLTが楽しくて複数回作ったり消したりしていたら「dlt-dlt*」で始まるS3バケット(削除マーカー含む)が大量に増殖&残存していたため、今回のやり方で環境を掃除しました。

https://dev.classmethod.jp/articles/load-ec2-with-dlt/

本記事がどなたかのお役に立てれば幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.